Skip to content

feat(reference): replace the humanizer dependency with /prose-editing - #89

Merged
schultzp2020 merged 8 commits into
mainfrom
feat/prose-clean
Aug 17, 2026
Merged

feat(reference): replace the humanizer dependency with /prose-editing#89
schultzp2020 merged 8 commits into
mainfrom
feat/prose-clean

Conversation

@schultzp2020

@schultzp2020 schultzp2020 commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Replaces the external /humanizer dependency with an in-pack reference skill,
/prose-editing, plus a human-invoked wrapper, /clean-prose. Recorded in
ADR-0010.

This started as "add an STE skill beside humanizer". Measuring the two together
showed they were not complements. A sample holding seventeen distinct AI writing
tells scored 5.93 against the STE linter's 2.5 bar — so it failed — but every
flagged category was incidental: four contractions, passive voice, a
nominalization, a long paragraph. Not one of the seventeen tells was detected.
An agent repairing what the linter reported would have returned prose that was
still obviously machine-written, carrying a passing score. Two skills also
claimed the same utterances, which ADR-0005 forbids.

The design

STE and humanizer do not disagree about tells. They disagree about
compression. That gives three layers and four routes:

Register The document is Compression Voice
strict a procedure, runbook, safety note, error string full off
flavored a README, docs page, PR body, changelog, Jira description partial off
voiced a release announcement, blog post, bylined prose off on
review anything that must not be rewritten reported reported

Mechanical tells score in every register. The register is inferred from document
type, never asked, and ambiguity resolves toward the register that changes
least — so a CVE changelog keeps may allow remote code execution. A caller
that knows names its own: /rhdh-pr-review uses flavored,
/rhdh-release-announce uses voiced.

The score is a delta, not a gate. A fixed bar rejected prose this repository
already treats as good, so the skill reports before and after. The bar survives
only as --fail-over N for a corpus a human owns.

Licensing

The upstream catalogue derives from Wikipedia's CC BY-SA Signs of AI writing,
which an Apache-2.0 repository cannot absorb. The taxonomy is taken and every
rule restated here; a 7-gram overlap scan against both sources confirms the only
remaining matches are watched-phrase list entries, which are data.
skills/reference/prose-editing/scripts/NOTICE carries the full MIT terms of
both upstream projects.

Also in this PR

A git bug that corrupts the developer's checkout. The pre-commit hook exports
GIT_DIR, so the unprotected git init in the test suite retargeted the real
repository, marked it core.bare=true, and pointed the following git add . at
the real index. Both call sites were exposed; tests/conftest.py was the worse
one because it is a shared fixture that runs init, add, and commit. Fixed with a
git_env() helper and a regression test that stands where a hook stands.

Two validator gaps. A skill with no ## headings could escape the
## Completion requirement entirely; the delegating-wrapper shape is now narrow
enough that a real skill cannot fall into it, and it is checked instead for a
delegate that exists and is model-invoked. Separately, named-invocation checking
assumed an rhdh- prefix, so renaming a generically named skill
(prose-editing, mutation-gate, skill-authoring) left its callers dangling
silently.

Test plan

  • uv run pytest — 446 passed, 6 skipped
  • uv run python scripts/validate_skill_catalog.py --json — valid, 43 skills, 3 human-invoked
  • python skills/reference/prose-editing/scripts/lint.py --help lists --register, --quote-safe, --baseline, --fail-over
  • Every documented example in references/ scores the category it teaches (pinned by test_every_documented_example_scores_the_category_it_teaches)
  • /rhdh-pr-review and /rhdh-release-announce name their registers and no longer reference /humanizer

Known limitations

Worth knowing before setting bars: on this corpus em_dash and long_sentence
are 50.7% of all violations, while everything a reader would call an AI tell
totals 18 of 3304 (0.5%). That is largely because the repository's prose is
human-written and genuinely clean — but it means over_bar currently reports
em-dash and sentence-length density. Bars are left at 1.5 / 2.5 / 2.0 for a human
to calibrate.

title_case_heading now requires a capitalized minor word as evidence, which
drops it from 368 corpus hits to 2. A proper-noun lexicon cannot cover a product
namespace without decapitalizing Red Hat Advanced Cluster Security. Heading
case belongs in markdownlint rather than in a slop score.

Ship a generic technical-prose rewrite skill that STE-flavors a draft
and proves it with a stdlib linter, as a sibling to /humanizer.

Co-authored-by: Cursor <cursoragent@cursor.com>
schultzp2020 and others added 4 commits August 14, 2026 16:03
The skill should state the rewrite loop, not the kits that inspired it.

Co-authored-by: Cursor <cursoragent@cursor.com>
A git hook exports GIT_DIR, GIT_WORK_TREE and friends. Any `git` the test
suite runs while those are set operates on the hook's repository rather than
the temporary directory the test asked for: `git init` rewrites that
repository's config and marks a normal checkout core.bare=true, and the
`git add` that follows targets the real index.

Both call sites were exposed. The one in tests/conftest.py is the worse of
the two because it is a shared fixture that runs init, add, and commit.

Add git_env() to tests/conftest.py, which returns the environment minus the
variables git uses to locate a repository, and route both call sites through
it. Identity variables such as GIT_AUTHOR_NAME are kept because they do not
redirect anything.

The regression test stands where a hook stands, with GIT_DIR pointing at
another repository, and asserts git init both lands where it was told to and
leaves that other repository alone.
The pre-commit pytest hook runs with GIT_DIR set, so the unprotected git init
in the test suite retargets the checkout it is running in, marks it
core.bare=true, and fails. This branch cannot run its own hooks without that
fix, so it builds on it.

# Conflicts:
#	tests/unit/test_release_cli.py
Two skills carried the external /humanizer as a hard prerequisite while a
separate proposal added a Simplified Technical English skill beside it.
Measuring them showed they were not complements. A sample holding seventeen
distinct AI writing tells scored 5.93 against the STE linter's 2.5 bar, and
every flagged category was incidental: four contractions, passive voice, a
nominalization, a long paragraph. None of the seventeen tells was detected.
An agent repairing what the linter reported would return prose that was still
obviously machine-written, carrying a passing score.

Merge them into one model-invoked reference skill, /prose-editing, with a
human-invoked wrapper, /clean-prose, and remove the external dependency.

The two systems do not disagree about tells, they disagree about compression.
That gives three layers: mechanical tells scored in every register, compression
scored only where prose should be flat, voice scored only where prose is
allowed one. Registers are strict, flavored and voiced, inferred from document
type, plus a read-only review route. Ambiguity resolves toward the register
that changes least, so a CVE changelog keeps "may allow remote code execution".
A caller that knows names its register: rhdh-pr-review uses flavored,
rhdh-release-announce uses voiced.

The score is a delta rather than a gate. A fixed bar rejected prose this
repository already treats as good, so the skill reports before and after and
the bar survives only as --fail-over for a corpus a human owns.

Prose is restated rather than copied. The upstream catalogue derives from
Wikipedia's CC BY-SA "Signs of AI writing", which Apache-2.0 cannot absorb, so
the taxonomy is taken and every rule written here. scripts/NOTICE carries the
MIT terms of both upstream projects.

The catalog validator learns the delegating wrapper: a body of one short line
naming exactly one model-invoked skill owes no Completion section, and is
checked instead for a delegate that exists and can be reached. Named-invocation
checking no longer assumes an rhdh- prefix, so renaming a generically named
skill can no longer leave its callers dangling.
@schultzp2020 schultzp2020 changed the title feat(meta): add prose-clean skill with bundled slop linter feat(reference): replace the humanizer dependency with /prose-editing Aug 14, 2026
@schultzp2020
schultzp2020 marked this pull request as ready for review August 17, 2026 21:30
@schultzp2020
schultzp2020 merged commit b4f7b83 into main Aug 17, 2026
5 checks passed
@schultzp2020
schultzp2020 deleted the feat/prose-clean branch August 17, 2026 21:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant